#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Component.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A KnowledgeEngine Component is a GenericObject that
#               forms the base class for all Components that are
#               used with in Model during the assessment process.
#
#               The distingusihing feature of Components is that
#               they support 'showif' using KERL (the Knowledge Engine
#               Rule Language) and base support for KERF (the Knowledge
#               Engine Report Formating) system.
#
# See Also:     GenericObject, Model
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.AssessmentComponentState import AssessmentComponentState
from Common import *
from Core import *
from GenericObject import GenericObject, GenericObjectInfo
from ReadableScore import ReadableScore, ReadableScoreInfo
from Products.KnowledgeEngine.Model.Model import Model
from PropertyDefinition import *
from PropertyTypes import *
from PropertyFormats import *
from PropertyStructures import *
from KERFProcessor import *
from KERLProcessor import *
####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile
from Acquisition import *

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## ComponentInfo Class Definition
class ComponentInfo(GenericObjectInfo, ReadableScoreInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'Component'

    #---------------------------------------------------------------#

    def getClassFamilyName(self):
        """
            Return the name of the family to which the the
            class (and subclasses) and it's subclasses belong/
            
            This is used to enable searching for families of 
            generic objects, eg: 'EntitySource' instances.
        """

        #all entity sources belong to the 'EntitySource' family
        return 'Component'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "Component"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.

            This is a short string used for prefixing object identities
        """

        return "component"

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className == 'Component' or GenericObjectInfo.isInstanceOf(self, className) or (self.isContainer() and ReadableScoreInfo.isInstanceOf(self, className))

    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in
            the specified container (containerInstance)

            NOTE: This is from this class' instance perspective.
        """

        #DEFAULT RULE: Components are permitted in
        #               a). Components that are containers, OR
        #               b). Containers that permit Components (this allows for special containers like Models to contain Components)
        return (containerClassInfo.isInstanceOf('Component') and containerClassInfo.isContainer()) or containerClassInfo.isChildPermitted(self, instance)

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #DEFAULT RULE: Only Component containers can contain children that are are components.
        return self.isContainer() and childClassInfo.isInstanceOf('Component')

    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific properties using the PropertyReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class properties for reflection
        GenericObjectInfo.onConfigureProperties(self)

        #add the 'guidance' property
        self.addPropertyDefinition(PropertyDefinition(identity="guidance", 
                                                      displayName="Guidance", 
                                                      description="Information to help the end-user with this component", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_HTML, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'order' property
        self.addPropertyDefinition(PropertyDefinition(identity="order", 
                                                      displayName="Order", 
                                                      description="The relative position of the component with in it's container", 
                                                      defaultValue=1.0, 
                                                      propertyType=pt_FLOAT, 
                                                      propertyFormat=pf_NATIVE, 
                                                      propertyStructure=ps_ATOMIC, 
                                                      constructorEVAP=evap_NONE, 
                                                      propertyEditorEVAP=evap_NONE))

        #add the 'showif' property
        self.addPropertyDefinition(PropertyDefinition(identity="showif", 
                                                      displayName="Show If...", 
                                                      description="The Knowledge Engine Rule Language Expression (KERL) that defines when this component should be displayed", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_MULTILINE, 
                                                      propertyStructure=ps_ATOMIC))
                                                      
                                                      
        #add the 'editif' property
        self.addPropertyDefinition(PropertyDefinition(
            identity="editif", 
            displayName="Edit If...", 
            description="The Knowledge Engine Rule Language Expression (KERL) that defines when this component should be editable", 
            defaultValue="", 
            propertyType=pt_STRING, 
            propertyFormat=pf_TEXT_MULTILINE, 
            propertyStructure=ps_ATOMIC
        ))

        #add the 'reference' property
        self.addPropertyDefinition(PropertyDefinition(identity="reference", 
                                                      displayName="Reference", 
                                                      description="Text that provides external reference information that was used to define this component", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'content' property
        self.addPropertyDefinition(PropertyDefinition(identity="content", 
                                                      displayName="Content", 
                                                      description="Text to be displayed with this component", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_HTML, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'reportTitle' property
        self.addPropertyDefinition(PropertyDefinition(identity="reportTitle", 
                                                      displayName="Report Title", 
                                                      description="The title to be used when this component is displayed in a report. (leave blank to use the component title)", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_HTML_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'reportContent' property
        self.addPropertyDefinition(PropertyDefinition(identity="reportContent", 
                                                      displayName="Report Content", 
                                                      description="The following content will appear in the report below the Report Title.", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_HTML, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'Search Index' property
        # TODO: Need a validator to make sure the search index is a safe value
        self.addPropertyDefinition(PropertyDefinition(identity="searchIndex", 
                                                      displayName="Search Index",
                                                      description="Index this field under the following Search Index. Can be used to perform Cross-model queries",
                                                      defaultValue="",
                                                      propertyType=pt_STRING,
                                                      propertyFormat=pf_TEXT_SINGLELINE,
                                                      propertyStructure=ps_ATOMIC))
                                                      
                                                      
                                                      
        
        #add the 'Mandatory' property
        self.addPropertyDefinition(PropertyDefinition(identity="isMandatory", 
                                                      displayName="Mandatory",
                                                      description="Prevent the user from continuing until this question is answered",
                                                      defaultValue=false,
                                                      propertyType=pt_BOOLEAN,
                                                      propertyFormat=pf_CHECKBOX))        
                                                      
        self.addPropertyDefinition(PropertyDefinition(
            identity       = "isUnique", 
            displayName    = "Unique",
            description    = "Force value to be unique across all assessments",
            defaultValue   = False,
            propertyType   = pt_BOOLEAN,
            propertyFormat = pf_CHECKBOX
        ))
        
        self.addPropertyDefinition(PropertyDefinition(
            identity       = "isDisabled", 
            displayName    = "Disable this component",
            description    = "Remove this component from display in assessments and reports",
            defaultValue   = False,
            propertyType   = pt_BOOLEAN,
            propertyFormat = pf_CHECKBOX
        ))        
        

        #add the 'assessmentDisplayTemplate' property
        self.addPropertyDefinition(PropertyDefinition(identity="assessmentDisplayTemplate", 
                                                      displayName="Assessment Display Template", 
                                                      description="The name of the page template used to display this component in an assessment. Leave empty to use default.", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'assessmentDisplayTemplate' property
        self.addPropertyDefinition(PropertyDefinition(identity="reportDisplayTemplate", 
                                                      displayName="Report Display Template", 
                                                      description="The name of the page template used to display this component in a report. Leave empty to use default.", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))

    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific events using the EventReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class permissions for reflection
        GenericObjectInfo.onConfigureEvents(self)

    #---------------------------------------------------------------#

    def onConfigurePermissions(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific permission definitions.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class events for reflection
        GenericObjectInfo.onConfigurePermissions(self)

    #---------------------------------------------------------------#
    

    

#####################################################################
## Component Class Definition
class Component(GenericObject):
    """
        Base class for all KnowledgeEngine Components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = ComponentInfo()

    #---------------------------------------------------------------#
    # Volatile Attributes
    _v_ShowIfKERL = None        #internal representation of the showif KERL
    
    
    
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the component"

        GenericObject.__init__(self, id, identity)

        self._v_ShowIfKERL = None
        self._v_Model = None

        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation processing
        GenericObject.onAfterInstanciation(self)

        #if the reportContent attribute is missing, we should now add it with the regular content as the default
        #this is so that we can maintain backwards compatibility with old reports
        if not hasattr(self, "reportContent"):
            if hasattr(self, "content"):
                #set the default reportContent to be the standard content
                self.reportContent = self.content
            else:
                self.reportContent = ""

        ###print "DEBUG: Component.onAfterInstanciation: Completed"

    #---------------------------------------------------------------#
    
    def getReportDisplayTemplate(self):
        """
            Returns the reportDisplayTemplate for this property
        """
        
        try:
            return self.reportDisplayTemplate
        except AttributeError:
            return None

    #---------------------------------------------------------------#
    
    def getAssessmentDisplayTemplate(self):
        """
            Returns the assessmentDisplayTemplate for this property
        """
        
        try:
            return self.assessmentDisplayTemplate
        except AttributeError:
            return None

    #---------------------------------------------------------------#
    
    def getSearchIndex(self):
        """ 
            Returns the searchIndex for this component 
        """
        try:
            return self.searchIndex
        except AttributeError:
            return None
    
    #---------------------------------------------------------------#        
    
    def setSearchIndex(self, searchIndex):
        """
            Sets the search index for this component 
        """
        self.searchIndex = searchIndex
    
    #---------------------------------------------------------------#
    
    def getIsMandatory(self):
        """
            Returns True if this question is mandatory.
            
            This should be deprecated ... badly named.
        """
        
        return getattr(self.aq_explicit, 'isMandatory', False)
        
    #---------------------------------------------------------------#
            
    def getIsDisabled(self):
        """
            Returns True if this question is mandatory.
            
            This should be deprecated ... badly named.
        """
        
        return getattr(self.aq_explicit, 'isDisabled', False)
        
    #---------------------------------------------------------------#                    
    
    def setIsMandatory(self, value):
        """ Sets the mandatory state of this component """
        
        self.isMandatory = value
        
    
    #---------------------------------------------------------------#    
    
    def getIsUnique(self):
        """ Returns True if this question is mandatory """
        
        return getattr(self.aq_explicit, 'isUnique', False)
        
    #---------------------------------------------------------------#        
    
    def setIsUnique(self, value):
        """ Sets the mandatory state of this component """
        
        self.isUnique = value
        
    
    #---------------------------------------------------------------#    

    def getParents(self):
        """
            Returns a list of parent Component Containers
            (not including the Model, which isn't a Component
             Container anyway!)
        """

        if isinstance(self.getParent(), Model):
            return []

        else:
            return [self.getParent()] + self.getParent().getParents()

    #---------------------------------------------------------------#

    def isDescendant(self, ancestor):
        """
            Returns if the component is a descendant of the specified ancestor
            (also returns true if self and the ancestor are the same!)
        """

        return ancestor in self.getParents() or self is ancestor

    #---------------------------------------------------------------#

    def getChildren(self, includeDisabled=False):
        """
            Returns a sequence of the immediate child Components
            of this container, sorted by the Component.getOrder()
        """

        #is the component a container?
        if self.isContainer():
            #get the immediate children of the container
            children = self.objectValues()

            #filter the Components only
            components = filter(lambda x : isinstance(x, Component) and (includeDisabled or not x.getIsDisabled()), children)

            #sort the component by Component.getOrder()
            components.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

            #ensure if the container is ordered, that the children have unique positions
            if self.isOrderedContainer():
                lastChild = None
                for child in components:
                    if lastChild == None:
                        lastChild = child
                    else:
                        isAscending = lastChild.getOrder() < child.getOrder()

                        if not isAscending:
                            child.setOrder(lastChild.getOrder() + 1)

                        lastChild = child

            return components

        else:
            return []

    #---------------------------------------------------------------#

    def getBaseParent(self):
        """
            Returns the parent component container that is the
            immediate child of the model in which this component
            is contained.

            RETURNS: None if the parent of this component is a Model
        """

        #get all of the parents (until the Model)
        parents = self.getParents()

        if len(parents) == 0:
            return None
        else:
            return parents[len(parents)-1]

    #---------------------------------------------------------------#

    def getPage(self):
        """
            Returns the page on which this component is located
            (taking promotion into account)

            May return self if the component has been promoted to
            the top-level!
        """

        #create a stack of possible pages... starting with this component
        componentStack = [self] + self.getParents()
        page = None

        while page is None:
            #pop the top-level parent
            parent = componentStack.pop()

            #is this the last parent?
            if len(componentStack) == 0:
                page = parent

            else:
                #is the parent a section?
                if parent.isInstanceOf("Section"):

                    #is the section promoting children?
                    if parent.getIsPromotingChildren():
                        #we should step to the next parent down, as
                        #we need to promote it.
                        pass

                    else:
                        #the section isn't promoting, so it's the top-level page
                        page = parent

                else:
                    #as it's not a section, it must be a page
                    page = parent

        return page

    #---------------------------------------------------------------#

    def getGuidance(self):
        "Returns the Guidance / Help for the object"
        return self.guidance

    #---------------------------------------------------------------#

    def setGuidance(self, guidance):
        "Set the Guidance / Help for the object"
        self.guidance = guidance

    #---------------------------------------------------------------#

    def getShowIf(self):
        "Returns the ShowIf expression of the component"
        return self.showif

    #---------------------------------------------------------------#

    def setShowIf(self, showif):
        "Set the ShowIf expression of the component"
        self.showif = showif

    #---------------------------------------------------------------#

    def hasShowIf(self):
        "Return if the component has a locally defined showif"
        return len(self.showif) > 0

    #---------------------------------------------------------------#
    
    def getEditIf(self):
        "Returns the EditIf expression of the component"
        return getattr(self, 'editif', '')

    #---------------------------------------------------------------#

    def setEditIf(self, editif):
        "Set the EditIf expression of the component"
        self.editif = editif

    #---------------------------------------------------------------#

    def hasEditIf(self):
        "Return if the component has a locally defined editif"
        return (getattr(self, 'editif',None)) and True or False

    #---------------------------------------------------------------#    

    def getReference(self):
        "Returns the user defined Reference for the component"
        return self.reference

    #---------------------------------------------------------------#

    def setReference(self, reference):
        "Set the user defined Reference for the component"
        self.reference = reference

    #---------------------------------------------------------------#

    def getContent(self):
        "Returns the content the component"
        return self.content

    #---------------------------------------------------------------#

    def setContent(self, content):
        "Set the content the component"
        self.content = content

    #---------------------------------------------------------------#

    def hasContent(self):
        "Returns if the object has content"
        return len(self.getContent()) > 0

    #---------------------------------------------------------------#

    def getTitleOrContent(self):
        """
            Returns the component title (if it has one) or
            alternatively returns the component content
        """

        if self.hasTitle():
            return self.getTitle()
        else:
            return self.getContent()

    #---------------------------------------------------------------#

    def getContentOrTitle(self):
        """
            Returns the component content (if it has some) or
            alternatively returns the component title
        """

        if self.hasContent():
            return self.getContent()
        else:
            return self.getTitle()

    #---------------------------------------------------------------#

    def getReportTitle(self):
        "Returns the Report Title the component"
        return self.reportTitle

    #---------------------------------------------------------------#

    def setReportTitle(self, reportTitle):
        "Set the Report Title for the component"
        self.reportTitle = reportTitle

    #---------------------------------------------------------------#

    def getReportContent(self):
        "Returns the reportContent the component"
        return self.reportContent

    #---------------------------------------------------------------#
    
    def getReportContentForValue(self, value):
        """ Returns the report content when the component value is the specified value 
            (Usually called alongside the standard report content, so if no specific report content
             is specified, should return an empty string rather than the default report content)
        """
    
        return self.getReportContent() # By default, components just have the one report content regardless of answer.
                  
    
    #---------------------------------------------------------------#
    
    def getAnswerReportContent( self, assessment ):
        """
            Evalutes the answer report content to display

            RETURN TYPE: string        
        """
        
        return self.getReportContentForValue( self.getComponentValue( assessment ) )
        
    
    #---------------------------------------------------------------#
    


    def setReportContent(self, reportContent):
        "Set the reportContent the component"
        self.reportContent = reportContent

    #---------------------------------------------------------------#

    def hasReportContent(self):
        "Returns if the object has reportContent"
        return len(self.getReportContent()) > 0

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the
            identity of this object is changed.

            Called By GenericObject.onAfterClone
        """

        #perform normal onBeforeIdentityChange processing
        GenericObject.onBeforeIdentityChange(self)

        try:
            ## The following code prevents duplicate Component cache entries
            ## being created in Models when a Component is copied between
            ## Models.
            ##
            ## That is, one Component with two different Identities in the
            ## target Model.
            ##
            ## The problem is caused by the Zope platform performing a copy,
            ## then an add before the clone callback is called.
            ## The process is as follows;
            ## 1. copy is created (without changing Identities... no problem)
            ## 2. copy is added into the target indexed container, which is different
            ##    from the indexed container being copied from.  Because the component
            ##    doesn't exist in the new indexed container, the component is
            ##    added to the cache, with the original identity!
            ## 3. the onAfterClone callback is performed, which allocates
            ##    new identities to the copies.  The component is then added to
            ##    the target indexed container cache (again), but this time with a
            ##    new identity... there you go, one component, two entries in an
            ##    indexed container cache.

            indexedcontainer = self.getModel()

            #get the component with this component's identity from the model
            component = indexedcontainer.getComponent(self.getIdentity())

            #is the component with this component's identity, this component?
            #(if it is, this component is already in the indexedcontainer, so remove it)
            if self == component:
                #remove the component, as it will be added with a new identity
                #by the Component.onAfterClone
                indexedcontainer.onBeforeDeleteComponent(self)

        except:
            pass

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the
            identity of this object has changed.

            The identityMappings dictionary holds
            mappings from oldIdentities -> newIdentities
            (including this object)

        """

        ###print "DEBUG: Component.onAfterIdentityChange: Commenced"

        #perform default processing
        GenericObject.onAfterIdentityChange(self, identityMappings)

        indexedContainer = self.getModel()

        #resolve the showif rule (if the component has one).
        if self.getShowIfKERL() != None:
    
            #resolve the showif kerl
            showIfKERL = updateKERLIdentities(self.getShowIfKERL(), identityMappings, indexedContainer)

            if showIfKERL:
                self.setShowIf(repr(showIfKERL))
            
        # TODO - Do update EditIf identities
                        
                

        #resolve report KERFs
        self.setReportTitle(updateKERF(self.getReportTitle(), identityMappings))
        self.setReportContent(updateKERF(self.getReportContent(), identityMappings))

        ###print "DEBUG: Component.onAfterIdentityChange mappings %s" % (repr(identityMappings),)

        ###print "DEBUG: Component.onAfterIdentityChange: Completed"

    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = None):
        """
            Handle Knowledge Engine specific actions after this component
            has been cloned
        """
        
        if identityMappings is None: identityMappings = {} 

        #perform normal onAfterClone processing
        GenericObject.onAfterClone(self, identityMappings)

        #notify the model that the component has been added (it will have a new identity!)
        try:
            self.getModel().onAfterAddComponent(self)
        except:
            pass

        ###print "DEBUG: Component.onAfterClone: Completed"

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        """
            Handle Knowledge Engine specific actions after this component
            has been added to a container.
        """

        ###print "DEBUG: Component.onAfterAdd: Commenced"

        #perform normal onAfterAdd processing
        GenericObject.onAfterAdd(self)

        #notify the model that the component has been added
        try:
            self.getModel().onAfterAddComponent(self)
        except:
            pass

        ###print "DEBUG: Component.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        """
            Handle Knowledge Engine specific actions before this component is deleted
        """

        ###print "DEBUG: Component.onBeforeDelete: Commenced"

        #jamesd 20060627 - This triggers when renaming, so dont start cleaning out the database...
        #is the component assessible? (remove assessment state for the component)
        #if self.isInstanceOf('Assessible'):
        #use the assessment storage service to remove the state for the component
        #    self.getAssessmentStorageService().deleteAllComponentState(self)

        #notify the model that the component is about to be deleted
        try:
            self.getModel().onBeforeDeleteComponent(self)
        except:
            pass

        #perform normal onBeforeDelete processing
        GenericObject.onBeforeDelete(self)

        ###print "DEBUG: Component.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def onBeforeShowIfChanged(self):
        """
            FINAL: Handle changing the showif expression for a component
        """

        #show if has now changed
        self._v_ShowIfKERL = None

        ###print "DEBUG: Component.onBeforeShowIfChanged() Completed"

    #---------------------------------------------------------------#

    def onAfterShowIfChanged(self):
        """
            FINAL: Handle changing the showif expression for a component
        """

        ###print "DEBUG: Component.onAfterShowIfChanged() Completed"

    #---------------------------------------------------------------#

    def getShowIfKERL(self):
        """
            FINAL: Returns a KERL Expression representing the
            optimized showif expression.
        """

        #do we have a KERL for the showif?
        if self._v_ShowIfKERL == None:
            
            # jamesd 2006-11-23: Fixed typo - was _v_showIFKerl (Uppercase F)
            self._v_ShowIfKERL = parseKERL(self.getShowIf())

        return self._v_ShowIfKERL

    #---------------------------------------------------------------#

    def getEditIfKERL(self):
        """
            FINAL: Returns a KERL Expression representing the
            optimized editif expression.
        """

        #do we have a KERL for the showif?
        if getattr(self,'_v_EditIfKERL', None) is None:
            self._v_EditIfKERL = parseKERL(self.getEditIf())

        return self._v_EditIfKERL


    #---------------------------------------------------------------#

    
    def getShowIfJavaScriptCondition(self, assessment, page = None):
        """
            FINAL: Returns the JavaScript Guard (boolean expression) for the showif for the
            component.
        """
        
        #get the page we are on (if not specified)
        if page is None:
            page = self.getPage()
        
        #get the transitive closure for the component
        transitiveClosure = getComponentShowIfTransitiveClosure(self, self.getModel(), assessment, page=page)
  
        #optimize the showif for the specified page and assessment
        optimizedShowIf = optimizeKERLForPageWithAssessment(transitiveClosure, assessment, page, component=self)
        
        #determine the javascript showif condition for the optimized showif
        javascriptCondition = compileKERLtoJavaScript(optimizedShowIf, self.getModel())

        #return an appropriate condition
        if javascriptCondition == "":
            return "true"
        else:
            return javascriptCondition

    #---------------------------------------------------------------#

    def getShowIfJavaScript(self, assessment):
        """
            FINAL: Returns the JavaScript for the showif for the
            component.

            RETURNS: document.getElementById("span" + getIdentity() + ").style.display = (' + showifCondition + ' ? "inline" : "none");
        """

        if not self.isShowIfSupported():
            return "// Ignoring component %s, showIf not supported" % self.getIdentity()
        

        #determine the showif condition for the component
        showifCondition = self.getShowIfJavaScriptCondition(assessment)

        #return the javascript showif
        return "document.getElementById(\"span" + self.getIdentity() + "\").style.display = (" + showifCondition + " ? \"inline\" : \"none\");"

    #---------------------------------------------------------------#

    def getEditIfJavaScript(self, assessment):
        """
            FINAL: Returns the JavaScript for the showif for the
            component.

            RETURNS: document.getElementById("span" + getIdentity() + ").style.display = (' + showifCondition + ' ? "inline" : "none");
        """

        if not self.isEditIfSupported():
            return "// Ignoring component %s, showIf not supported" % self.getIdentity()
        
        
        kerl = self.getEditIfKERL()

        page = self.getPage()

        optimized = optimizeKERLForPageWithAssessment( kerl, assessment, page, component=self )

        javascript = compileKERLtoJavaScript( optimized, self.getModel(), component=self ) or '"true"'
        
        return "setComponentEnabled('%s', %s ? true : false )" % ( self.getIdentity(), javascript )


    #---------------------------------------------------------------#
    

    def isShowIfSupported(self):
        """ 
            Is show if supported for this component?
            (Used for the Option component to return False on MultiSelects)
            
            RETURNS: Bool
        """
        
        return True


    #---------------------------------------------------------------#
    

    def isEditIfSupported(self):
        """ 
            Is show if supported for this component?
            (Used for the Option component to return False on MultiSelects)
            
            RETURNS: Bool
        """
        
        return True

    #---------------------------------------------------------------#

    def isShown(self, assessment):
        """
            FINAL: Evaluates (on the server-side) and returns if the
            component is to be shown based on the components
            show if expression.
        """

        # Disabled components are never shown
        if self.getIsDisabled():
            return False

        #determine the showif condition for the component
        showIfExpression = getComponentShowIfTransitiveClosure(self, self.getModel(), assessment)

        #evaluate the expression using the provided assessment
        return evaluate(showIfExpression, assessment)
    
    #---------------------------------------------------------------#

    def isEditable(self, assessment):
        """
            FINAL: Evaluates (on the server-side) and returns if the
            component is to be editable based on the components
            show if expression.
        """

        if self.getIsDisabled():
            return False

        #determine the showif condition for the component
        editIfExpression = parseKERL( self.getEditIf() )
        
        #evaluate the expression using the provided assessment
        result = evaluate(editIfExpression, assessment)    
        
        if result == 'never':
            return False
        elif result == 'always':
            return True

        #return result

    #---------------------------------------------------------------#

    def onBeforePropertyChange(self, propertyName, newValue):
        "Handle when an object property is about to change (using the property editor)"

        #has the showif changed?
        hasShowIfChanged = propertyName == "showif" and hasattr(self, "showif")
        if hasShowIfChanged:
            self.onBeforeShowIfChanged()

    #---------------------------------------------------------------#

    def onAfterPropertyChange(self, propertyName, oldValue, newValue):
        "Handle when an object property has changed (using the property editor)"

        #has the showif changed?
        hasShowIfChanged = propertyName == "showif" and hasattr(self, "showif")
        if hasShowIfChanged:
            self.onAfterShowIfChanged()

    #---------------------------------------------------------------#

    def getComponentProperty(self, identity, propertyName, assessment = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment.

            RETURNS: None if the property doesn't exist
        """

        if propertyName == "title":
            return self.getTitle()
        else:
            #see if the property can be gained using the Property Manager
            if self.hasProperty(propertyName):
                return self.getProperty(propertyName)
            else:
                return None

    #---------------------------------------------------------------#

    def isPreviewPermitted(self):
        """
            Return if the component is allowed to be previewed.

            By default all components can be previewed, some however
            should not be previewed. eg: Options whose display is
            dependant on their parents.
        """

        return true

    #---------------------------------------------------------------#

    def addJavaScriptOnLoadStatements(self, onLoadStatementsDictionary):
        """
            ABSTRACT: Adds the necessary onLoad statements to the
            onLoadStatementsDictionary which is defined as {KEY -> string}.

            The KEY is used to determine which component generated
            the statements.  The KEY may be used to determine if
            statements have already been generated for this type
            of component.

            The STRING is the actual javascript statements.

            NOTE: When overriding, this method should always be called
            from sub-classes.
        """

        #most components don't require onLoad statements
        pass

    #---------------------------------------------------------------#

    def addJavaScriptDeclarations(self, declarationsDictionary):
        """
            ABSTRACT: Adds the necessary javascript declarations to
            the declarationsDictionary which is defined as {KEY -> string}.

            The KEY is used to determine which component generated
            the declarations.  The KEY may be used to determine if
            declarations have already been generated for this type
            of component.

            The STRING is the actual javascript declaration(s).

            NOTE: When overriding, this method should always be called
            from sub-classes.
        """

        #most components don't require declarations
        pass

    #---------------------------------------------------------------#

    def guidance_link(self, REQUEST, **args):
        """
            CMF Support:

            A wrapper over the Component_guidance_link.dtml DTML document
        """

        return self.Component_guidance_link(self, REQUEST, **args)

    #---------------------------------------------------------------#

    def guidance_view(self, REQUEST, **args):
        """
            CMF Support:

            A wrapper over the Component_guidance_view.dtml DTML document
        """

        return self.Component_guidance_view(self, REQUEST, **args)

    #---------------------------------------------------------------#

    def simplereport_view(self, REQUEST, **args):
        """
            CMF Support:

            Return the default CMF <object>_simplereport_view.dtml
        """

        #dynamically determine the view function for this object
        return eval("self.%s_simplereport_view(self, REQUEST, **args)" % self.getClassInfo().getClassName())

    #---------------------------------------------------------------#

    def scoredreport_view(self, REQUEST, **args):
        """
            CMF Support:

            Return the default CMF <object>_scoredreport_view.dtml
        """

        #dynamically determine the view function for this object
        return eval("self.%s_scoredreport_view(self, REQUEST, **args)" % self.getClassInfo().getClassName())

    #---------------------------------------------------------------#

    def scored360report_view(self, REQUEST, **args):
        """
            CMF Support:

            Return the default CMF <object>_scored360report_view.dtml
        """

        #dynamically determine the view function for this object
        return eval("self.%s_scored360report_view(self, REQUEST, **args)" % self.getClassInfo().getClassName())

    #---------------------------------------------------------------#

    def getScore(self, assessment):
        """
            Return the score of the Component (if it's a container) with the
            specified assessment.
        """

        result = 0

        #return the sum of the child scores that implement readablescore AND are shown given the specified assessment
        if self.isContainer():
            for child in self.getChildren():
                if child.isInstanceOf("ReadableScore") and child.isShown(assessment):
                    result += child.getScore(assessment)

        return result

    #---------------------------------------------------------------#

    def getFirstComponentChild(self, assessment = None):
        """
            Returns the first ordered child in the container.
            (optionally using the assessment for
            dynamically controlled children)

            RETURNS: None if the aren't any ordered children or if the
            container isn't ordered
        """

        #is the container ordered?
        if self.isOrderedContainer():

            #get the ordered children
            siblings = [sibling for sibling
                        in self.getChildren()
                        if hasattr(sibling, 'isOrderable') and sibling.isOrderable()]

            #sort the siblings (in non-descending order)
            siblings.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

            #are there any siblings
            if len(siblings) == 0:
                return None
            else:
                return siblings[0]

        else:
            return None

    #---------------------------------------------------------------#

    def getComponentSiblingAfterChild(self, child, assessment = None):
        """
            Returns the child immediately after the specified child
            (optionally using the assessment for
            dynamically controlled children)

            RETURNS: None if the aren't any children after the specified
            child (or if the container isn't ordered)
        """

        #is the container ordered?
        if self.isOrderedContainer() and child.isOrderable():

            #get the ordered children that occur after the specified child
            nextSiblings = [sibling for sibling
                            in self.getChildren()
                            if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() > child.getOrder()]

            #sort the previous siblings (in non-descending order so the next sibling is first)
            nextSiblings.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

            #the specified child is the first and only in the list
            if len(nextSiblings) == 0:
                return None
            else:
                return nextSiblings[0]

        else:
            return None

    #---------------------------------------------------------------#

    def getLastComponentChild(self, assessment = None):
        """
            Returns the last ordered child in the container.
            (optionally using the assessment for
            dynamically controlled children)

            RETURNS: None if the aren't any ordered children or if the
            container isn't ordered
        """

        #is the container ordered?
        if self.isOrderedContainer():

            #get the ordered children
            siblings = [sibling for sibling
                        in self.getChildren()
                        if hasattr(sibling, 'isOrderable') and sibling.isOrderable()]

            #sort the siblings (in descending order so the previous sibling is first)
            siblings.sort(lambda x, y : -(x.getOrder() > y.getOrder()))

            #are there any siblings
            if len(siblings) == 0:
                return None
            else:
                return siblings[0]

        else:
            return None

    #---------------------------------------------------------------#

    def getComponentSiblingBeforeChild(self, child, assessment = None):
        """
            Returns the child immediately before the specified child
            (optionally using the assessment for
            dynamically controlled children)

            RETURNS: None if the aren't any children before the specified
            child (or if the container isn't ordered)
        """

        #is the container ordered?
        if self.isOrderedContainer() and child.isOrderable():

            #get the ordered children that occur before the specified child
            previousSiblings = [sibling for sibling
                                in self.getChildren()
                                if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() < child.getOrder()]

            #sort the previous siblings (in descending order so the previous sibling is first)
            previousSiblings.sort(lambda x, y : -(x.getOrder() > y.getOrder()))

            #the specified child is the first and only in the list
            if len(previousSiblings) == 0:
                return None
            else:
                return previousSiblings[0]

        else:
            return None

    #---------------------------------------------------------------#

    def getLastDescendantComponentChild(self, assessment = None):
        """
            Returns the last most descendant of the container

            (optionally using the assessment for
            dynamically controlled children)

            RETURNS: self if the aren't any children
            (or if the container isn't ordered)
        """

        #get the last child
        lastChild = self.getLastComponentChild(assessment)

        #is the last child None?
        if lastChild is None:
            return self
        elif lastChild.isInstanceOf("Component") and lastChild.isContainer():
            return lastChild.getLastDescendantComponentChild(assessment)
        else:
            return lastChild

    #---------------------------------------------------------------#

    def getNextComponentSibling(self, assessment = None):
        """
            Returns the next component sibling at this level

            RETURNS: None if this component is the last component sibling
        """

        #make sure the parent is a component container
        if self.getParent().isInstanceOf("Component") and self.getParent().isContainer():
            return self.getParent().getComponentSiblingAfterChild(self, assessment)

        else:
            return self.getNextSibling()

    #---------------------------------------------------------------#

    def getPreviousComponentSibling(self, assessment = None):
        """
            Returns the previous component sibling at this level

            RETURNS: None if this component is the first component sibling
        """

        #make sure the parent is a component container
        if self.getParent().isInstanceOf("Component") and self.getParent().isContainer():
            return self.getParent().getComponentSiblingBeforeChild(self, assessment)

        else:
            return self.getPreviousSibling()

    #---------------------------------------------------------------#

    def getNextComponent(self, assessment = None):
        """
            Returns the next component (doing a depth-first traversal)

            RETURNS: None if this component is the last component
        """

        #is this component a container?
        if self.isContainer():
            nextComponent = self.getFirstComponentChild(assessment)
        else:
            nextComponent = None

        #do we have a next sibling?
        if nextComponent is None:
            nextComponent = self.getNextComponentSibling(assessment)

        #do we have a next sibling?
        if nextComponent is None:
            #find a Component container parent with a next sibling
            parent = self.getParent()
            while parent.isInstanceOf("Component") and parent.isContainer() and nextComponent is None:

                #get the next sibling of the parent
                nextComponent = parent.getNextComponentSibling(assessment)

                #if the parent doesn't have a sibling, we need to keep looking up the tree
                if nextComponent is None:
                    parent = parent.getParent()

        return nextComponent

    #---------------------------------------------------------------#

    def getPreviousComponent(self, assessment = None):
        """
            Returns the previous component (doing a depth-first traversal)

            RETURNS: None if this component is the last component
        """

        previousSibling = self.getPreviousComponentSibling(assessment)

        #is there a previous sibling?
        if previousSibling is None:
            if self.getParent().isInstanceOf("Component") and self.getParent().isContainer():
                return self.getParent()
            else:
                return None

        elif previousSibling.isInstanceOf("Component") and previousSibling.isContainer():
            previousSibling = previousSibling.getLastDescendantComponentChild(assessment)

        return previousSibling

    #---------------------------------------------------------------#

    def getPreviousComponentByInstanceOf(self, classInfoClassName, assessment = None):
        """
            Attempts to locate a specified type of component in the
            tree doing a depth-first traversal backwards up the tree
            using the provided assessment
        """

        #start with the previous component
        previousComponent = self.getPreviousComponent(assessment)

        #search until either we've run out of components OR we find the one we are after
        while previousComponent is not None and previousComponent.getClassInfo().getClassName() != classInfoClassName:
            previousComponent = previousComponent.getPreviousComponent(assessment)

        return previousComponent

    #---------------------------------------------------------------#

    def testForwardTraversal(self, assessment=None):
        """
            Test the forward traversal with the specified assessment
        """

        print "Forward from Node: %s"% self.getTitle()
        nextComponent = self.getNextComponent(assessment)

        while nextComponent is not None:
            print "Node: %s"% nextComponent.getTitle()
            nextComponent = nextComponent.getNextComponent(assessment)

    #---------------------------------------------------------------#

    def testBackwardTraversal(self, assessment=None):
        """
            Test the backward traversal with the specified assessment
        """

        print "Backward from Node: %s"% self.getTitle()
        previousComponent = self.getPreviousComponent(assessment)

        while previousComponent is not None:
            print "Node: %s"% previousComponent.getTitle()
            previousComponent = previousComponent.getPreviousComponent(assessment)

    #---------------------------------------------------------------#

    def onBeforeRenderAssessmentEditor(self, request, **args):
        """
            This method is called by Component.renderAssessmentEditor
            prior to actually rendering the assessment editor
            for this component using the Page Template (defined
            for this component).

            By overriding this method, individual components
            have the ability to modify the request (add state,
            declarations etc) prior to rendering occuring.

            If you override this method, a call should be
            made to the super-class to ensure correct
            super-class initialisation.
        """

        #by default we don't do anything to the request
        pass

    #---------------------------------------------------------------#

    def renderAssessmentEditor(self, request, **args):
        """
            Handles the request to render this component
            in the assessment editor.

            Actual rendering of the assessment editor is performed
            in the Component_assessmenteditor.pt

            REQUEST contains the necessary environment
            variables, established by the Model.assess call.
        """

        #add the javascript declarations and initialisation for this component
        self.addJavaScriptOnLoadStatements(request['ONLOAD_JAVASCRIPT'])
        self.addJavaScriptDeclarations(request['DECLARATIONS_JAVASCRIPT'])

        #add the showif refresh javascript
        if not request['ISPREVIEW'] and self.hasShowIf():
            request['REFRESH_JAVASCRIPT'].append(self.getShowIfJavaScript(request['ASSESSMENT']))

        if not request['ISPREVIEW'] and self.hasEditIf():
            request['REFRESH_JAVASCRIPT'].append(self.getEditIfJavaScript(request['ASSESSMENT']))
    
        #have the component instance handle any last minute changes to the request before we render it
        self.onBeforeRenderAssessmentEditor(request, **args)


        # 20061011 [johnm]  Use the defined replacement assessment display template is available
        if self.getAssessmentDisplayTemplate():
            return eval("self.%s(self, REQUEST=request, **args)" % self.getAssessmentDisplayTemplate())
            

        #dynamically determine the assessment editor rendering page for this object
        return eval("self.%s_assessmenteditor(self, REQUEST=request, **args)" % self.getClassInfo().getClassName())

    #---------------------------------------------------------------#
    
    def getAssessmentWizardAndWizardState(self, assessment, wizardName, user=None):
        """
            ABSTRACT: Returns a tuple containing a Wizard instance and
            an associated WizardState instance (Wizard, WizardState)
            for the specified assessment and wizardName.
            
            This method is used to initialise a named assessment wizard
            that will be used during the assessment process on behalf
            of this component.
            
            A value of (None, None) is returned if there is no wizard
            available for the specified wizardName. (or assessment 
            wizards are not supported for the component)
            
            RETURN TYPE: (Wizard, WizardState)
        """ 

        return (None,None)

    #---------------------------------------------------------------#

    def executeAssessmentWizard(self, assessment, wizardName, wizard, wizardState):
        """
            ABSTRACT: Executes the specified assessment wizard with
            the provided wizard state.
            
            Override this method to implement assessment wizard processing
            
            RETURN TYPE: None
        """
        
        #by default we don't do anything
        pass

    #---------------------------------------------------------------#

    def assessmentwizard_form(self, REQUEST, **args):
        """
            PLONE Support:

            The purpose of this method is the handle POST only (we don't handle GETs)
            requests for generating and applying accessories via the
            Component_assessmentwizard_form.pt
        """
    
        #let's use a sensible case for the REQUEST
        request = REQUEST
    
        #get the session from the request
        session = request["SESSION"]
        
        #get the response from the request (weird I know, but that's where it lives in Zope)
        response = request["RESPONSE"]

        #get the user id of the currently authenticated user
        zopeUserIdentity = REQUEST["AUTHENTICATED_USER"].getId()
        userIdentity = iif(zopeUserIdentity is None, "anonymous", zopeUserIdentity)

        #get the User from the UserProviderService (we may even store it in the session!)
        userProviderService = getUserProviderService(self)
        user = userProviderService.getUser(userIdentity)
    
        #grab the wizardState from the session (if it's lost we are stuffed)
        #TODO: do something when the session state is lost 
        wizardState = session['wizardState']

        #retrieve the injected values so we know how to navigate
        wizardName = wizardState.getPropertyValue('wizardName')
        componentIdentity = wizardState.getPropertyValue('componentIdentity')
        assessmentIdentity = wizardState.getPropertyValue('assessmentIdentity')
        revision = wizardState.getPropertyValue('assessmentRevision')
        pageIdentity = wizardState.getPropertyValue('pageIdentity')
        openedFromRepositoryIdentity = wizardState.getPropertyValue('openedFrom')
        exitURL = wizardState.getPropertyValue('exitURL')

        #get the component based on the wizardState
        component = self.getModel().getComponent(componentIdentity)
        
        #get the assessment from the wizard
        storageService = getAssessmentStorageService(self)
        assessmentIdentity = wizardState.getPropertyValue('assessmentIdentity')
                        
        #load the assessment (so we can find out where it lives)
        assessment = storageService.loadAssessment(assessmentIdentity, self.getModel(), user.getIdentity(), openedFromRepositoryIdentity, revision)
        
        #get the wizard from the component
        wizardName = wizardState.getPropertyValue('wizardName')
        (wizard, _) = component.getAssessmentWizardAndWizardState(assessment, wizardName, user=user)

        #do we need to render the wizard editor? (assume no)
        renderWizard = false
        
        #do we need to execute the wizard? (assume no)
        executeWizard = false
 
        #do we need to process wizard state?
        if wizard.getTotalWizardSteps(wizardState, self) > 0:

            #determine all of the editlets for properties of the current wizardState, and have them work out and set the
            #appropriate property values in the wizardState using the information contained in the posted REQUEST.
            for propertyDefinition in wizard.getPropertyDefinitionsForWizardStep(wizardState, component):

                #get the property editlet (using the current value of the property)
                editlet = propertyDefinition.getEditlet(propertyAccess=wizardState, context=component)

                #does the property have an editlet
                if editlet != None:
                    #have the editlet set the new value from the request
                    editlet.updatePropertyWith(propertyDefinition, wizardState, REQUEST)
                else:
                    print 'no editlet for %s' % propertyDefinition.getIdentity()

            #what was the wizard action? cancel, back or next?
            #cancel?
            if request.has_key("button.cancel"):
                #nothing to do after this return to the assessment editor
                renderWizard = false
                executeWizard = false
                
            #back?
            elif request.has_key("button.back"):
                #always render on 'back'
                renderWizard = true

                #move backwards iff we aren't at the first step
                if not wizard.isAtFirstWizardStep(wizardState, self):
                    wizardState.previousWizardStep()

            #next?
            elif request.has_key("button.next"):
                #always render on 'next'
                renderWizard = true                            

                #move forwards iff we aren't at the last step
                if not wizard.isAtLastWizardStep(wizardState, self):
                    wizardState.nextWizardStep()

            #finished!                    
            else:
                #now execute the wizard
                renderWizard = false
                executeWizard = true

        #render the wizard step?
        if renderWizard:
            #update the session wizardState
            session['wizardState'] = wizardState
            
            #call the component Component_assessmentwizard_form kick-off the wizard
            return component.Component_assessmentwizard_form(REQUEST=request, EDITINGOBJECT=self, WIZARD=wizard, WIZARDSTATE=wizardState)

        else:
            #execute the wizard?
            if executeWizard:
                #have the component execute it's own wizard
                component.executeAssessmentWizard(assessment, wizardName, wizard, wizardState)
            
                #save the assessment after executing the wizard
                #
                # 20070327 johnm    We don't want to update the revision when
                #                   saving or else assessment components will
                #                   get out of sync with the wizard component
                #                   state.
                storageService.saveAssessment(assessment)
            
            #remove the wizard state from the session
            del session['wizardState']

            #return to the assessment editor (by changing the REQUEST and forwarding the call onto the Model.assess method)
            request['assessmentIdentity'] = assessmentIdentity
            request['openedFrom'] = openedFromRepositoryIdentity
            request['exitURL'] = exitURL
            request['ASSESSMENT_REVISION'] = revision
            request['pageIdentity'] = pageIdentity
            request['gotoaction'] = 'navigate'
            request['gotolocation'] = pageIdentity
            return component.getModel().assess(REQUEST=request)

    #---------------------------------------------------------------#
    
    def updateAssessmentState(self, assessment, formValue):
        """
            ABSTRACT: Updates the specified assessment using
            the provided HTTP REQUEST formValue.
            
            NOTE: This method MUST be overriden in the implementing
            class to suitably update the component state
        """
        
        
        #print "%s::updateAssessmentState( %s ) [%s]" % ( `self`, `formValue`, iif(self.getIsMandatory(), 'Mandatory', 'Optional'))
        
        if self.getIsMandatory() and not formValue:
            raise FieldRequiredException( "Field Required", component=self )
        
        # If we specify a form value and this field is flagged as unique, check for uniqueness and raise an exception if it's not.
        # (Empty or NULL values are not checked for uniqueness)
        ass = self.knowledgeengine_assessmentstorageservice
        if formValue and self.getIsUnique() and not ass.isValueUnique( component=self, assessment=assessment, formValue=formValue ):
            raise UniqueKeyException( "Field must be unique", component=self )
        
    
    #---------------------------------------------------------------#
    def getComponentValue( self, assessment ):
        """ Returns this components value for the specified assessment """
        
        state = assessment.getComponentState( self.getIdentity() )
        return state.getValue()
        
    
    #---------------------------------------------------------------#
    def getComponentCSVValue(self, assessment, displayReportContent=False):
        """
            Return the CSV friendly version of the component. Really
            only applies to Exportable components
        """
        
        value = self.getComponentValue(assessment)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # 
        # Do stuff to the value of the component here if you need to
        # 
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
        return value


    #---------------------------------------------------------------#
    def isMultiValued(self):
        """
            Returns if the component is expecting a list ie. can store
            multiple values
        """
        
        return false
    
    #---------------------------------------------------------------#
    
    def onAfterEdit(self):
        """ Called after a components properties are edited """
        
        print "Component::onAfterEdit"
        
        GenericObject.onAfterEdit( self )
        
        if self.getModel().getAutoCreateIndexes():
        
            ass = self.knowledgeengine_assessmentstorageservice
            
            oldSearchIndex = getattr( self, "_oldSearchIndex", None )
            newSearchIndex = self.getSearchIndex()
            
            print "New search index: %s" % newSearchIndex
            
            if newSearchIndex and not (newSearchIndex == oldSearchIndex):
                ass.createSearchIndex( newSearchIndex )
            
            self._oldSearchIndex = newSearchIndex
    
    
    
    
    #---------------------------------------------------------------#    
    
    def compileKERLtoJavaScript( self, expression ):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: String
        """
        
        identity = self.getIdentity()
        opr = expression[1]
        
        if opr == 'attempted':
            return "document.getElementById(\"" + identity + "\").value != 'undefined'"
        elif opr == 'notattempted':
            return "document.getElementById(\"" + identity + "\").value == 'undefined'"
            #return "document.getElementById(\"" + identity + "\").value == undefined"        
        else:            
            raise InvalidKerlOperator( opr )

    #---------------------------------------------------------------#

    def getComponentState(self, assessmentIdentity, valueType, value, searchIndex=None, searchValue=None, revision=None):
        """
            Return the stored state of the component. This can be
            overridden if the state represents something other than
            a primitive i.e. entities
            
            [Added 20070201 johnm]
        """

        return AssessmentComponentState(
            assessmentIdentity,
            self.getModel().getIdentity(),
            self.getIdentity(),
            valueType,
            value,
            searchIndex=searchIndex,
            searchValue=searchValue,
            revision=revision
        )
    
    #---------------------------------------------------------------#    

#####################################################################
## Class Initialisation
# (none)
